package com.tongda.core.message;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;

/**
 * 
 * 客户端协议处理
 * 
 * @author andy
 */
public class NativePortClient extends GeneralObserver implements Runnable {

	private boolean isPrepared = false;
	private Selector selector;
	private SelectionKey clientKey;
	private InetSocketAddress address;

	private static NativePortClient instance;

	private NativePortClient() {
	}

	public static NativePortClient getInstance(String host, int port) {
		if (instance == null) {
			synchronized (NativePortClient.class) {
				if (instance == null || instance != null
						&& !instance.isPrepared) {//
					instance = new NativePortClient(host, port);
				}
			}
		}
		return instance;
	}

	/**
	 * 客户端构造函数
	 * 
	 * @param host
	 *            服务器地址
	 * @param port
	 *            服务器端口
	 */
	private NativePortClient(String host, int port) {
		address = new InetSocketAddress(host, port);
		try {
			selector = Selector.open();
			System.err.println("Selector.open()");
		} catch (IOException e) {
			notifyStateChanged(ERROR, e);
			e.printStackTrace();
		}
	}

	public void start() {
		new Thread(this).start();
	}

	public void send(String msg) {
		notifyStateChanged(MSG_SEND, msg);
		if (null == clientKey) {
			return;
		}
		clientKey.attach(msg);
		clientKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
		clientKey.selector().wakeup();
	}

	@Override
	public void run() {
		try {
			System.out.println("NativePortClient run()");
			SocketChannel sc = SocketChannel.open();
			sc.configureBlocking(false);
			sc.connect(address);
			clientKey = sc.register(selector, SelectionKey.OP_CONNECT);
			isPrepared = true;
			try {
				while (isPrepared) {
					int keysCount = selector.select();
					if (keysCount < 1) {
						continue;
					}
					Set<SelectionKey> set = selector.selectedKeys();
					Iterator<SelectionKey> it = set.iterator();
					while (it.hasNext()) {
						SelectionKey key = it.next();
						if (key.isConnectable()) {
							doConnect(key);
						}
						if (key.isValid() && key.isReadable()) {
							try {
								doRead(key);
							} catch (Exception e) {
								System.out.println("doRead 网络异常消息丢失一次@"
										+ new Date().toLocaleString());
								doConnect(key);
								// e.printStackTrace();
							}
						}
						if (key.isValid() && key.isWritable()) {
							doWrite(key);
						}
					}
					set.clear();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		} catch (IOException e) {
			notifyStateChanged(ERROR, e);
			e.printStackTrace();
		} finally {
			// close();
			notifyStateChanged(CLT_DISCONNECT, null);
		}
	}

	public void close() {
		isPrepared = false;
		try {
			if (null != clientKey) {
				clientKey.channel().close();
			}
			if (null != selector) {
				selector.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void release() {
		close();
		instance = null;
	}

	public void reConnect() {

	}

	private void doConnect(SelectionKey key) {
		SocketChannel sc = (SocketChannel) key.channel();
		try {
			// http://www.velocityreviews.com/forums/t145075-whats-the-proper-way-to-use-socketchannel-finishconnect.html
			sc.finishConnect();
			key.interestOps(SelectionKey.OP_READ);
			notifyStateChanged(CLT_CONNECT, null);
			System.out.println("--------------doConnect");
		} catch (IOException e) {
			System.err.println("doConnect IOException");
			disconnect(key);
			e.printStackTrace();
		}
	}

	private void doRead(SelectionKey key) {
		SocketChannel sc = (SocketChannel) key.channel();
		ByteBuffer bb = ByteBuffer.allocate(BUFFERSIZE);
		StringBuffer sb = new StringBuffer();
		try {
			int count = 0;
			while ((count = sc.read(bb)) > 0) {
				bb.flip();
				sb.append(decoder.decode(bb));
			}
			if (count == -1) {
				disconnect(key);
			} else {
				notifyStateChanged(MSG_RECEIVE, sb.toString().trim());
			}
		} catch (IOException e) {
			disconnect(key);
			e.printStackTrace();
		}
	}

	private void doWrite(SelectionKey key) {
		SocketChannel sc = (SocketChannel) key.channel();
		String msg = (String) key.attachment();
		if (null == msg) {
			key.interestOps(SelectionKey.OP_READ);
			return;
		}
		try {
			sc.write(encoder.encode(CharBuffer.wrap(msg)));
		} catch (IOException e) {
			disconnect(key);
			e.printStackTrace();
		}
		key.interestOps(SelectionKey.OP_READ);
	}

	/** 断开连接 */
	private void disconnect(SelectionKey key) {
		notifyStateChanged(CLT_DISCONNECT, null);
		try {
			key.cancel();
			key.channel().close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public boolean isPrepared() {
		return isPrepared;
	}

	public void setPrepared(boolean isPrepared) {
		this.isPrepared = isPrepared;
	}

	public Selector getSelector() {
		return selector;
	}

	public void setSelector(Selector selector) {
		this.selector = selector;
	}

	public SelectionKey getClientKey() {
		return clientKey;
	}

	public void setClientKey(SelectionKey clientKey) {
		this.clientKey = clientKey;
	}

	public InetSocketAddress getAddress() {
		return address;
	}

	public void setAddress(InetSocketAddress address) {
		this.address = address;
	}

}
